//
//  HQCapture8ViewController.m
//  精通Objective-C
//
//  Created by 花强 on 2017/12/6.
//  Copyright © 2017年 花强. All rights reserved.
//

#import "HQCapture8ViewController.h"

#import <objc/runtime.h>
#import "C8DynamicClass.h"

//
@interface C8TestClass1:NSObject{
    @public
    int myInt;
}
@end

@implementation C8TestClass1

@end

@interface C8TestClass2:NSObject{
@public
    int myInt;
}
@end

@implementation C8TestClass2

@end

//

@interface C8Greeter:NSObject

@property (readwrite , strong) NSString * salutation;
-(NSString *)greeting:(NSString *)recipient;

@end

@implementation C8Greeter

-(NSString *)greeting:(NSString *)recipient{
    return [NSString stringWithFormat:@"%@,%@",[self salutation],recipient];
}

@end

@interface HQCapture8ViewController ()

@end

@implementation HQCapture8ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    /*
     
     第八章 运行时系统的结构
     
     运行时系统是OC平台的关键元素；
        OC语言的动态特性和面向对象功能就是由它实现的；
        运行时系统提供了公用API，是你编写的代码能够直接调用运行时系统服务；
     
     8.1 运行时系统的组成部分
     
     OC运行时系统的两个组成部分：编译器 和 运行时系统库；
     
     8.1.1 编译器
     
     第五章简述了源代码常规编译过程；
        编译进程会接收OC源文件并进行处理（该处理过程由词法分析、语法分析、代码生成和优化、汇编以及链接操作等阶段构成），生成构成可执行程序的二进制文件；
        和C语言标准库会为C语言程序提供标准API和实现代码一样，运行时系统库也会为OC的面向对象特性 提供标准的API和实现代码；
        这种库与所有OC程序链接（在链接阶段）；
        编译器的作用是接收输入的源代码，生成使用了运行时系统库的代码，从而得到合法的可执行的OC程序；
     
     OC语言中的面向对象元素和动态特性都是通过运行时系统实现的；
     运行时系统组成部分：
     1）类元素（接口、实现代码、协议、分类、方法、属性、实例变量）；
     2）类实例（对象）；
     3）对象消息传递（包括动态类型和动态绑定）；
     4）动态方法决议；
     5）动态加载；
     6）对象内省；
     
     简单来说，当编译器解析使用了这些语言元素和特性的OC源代码时，就会使用适当的运行时系统库数据结构和实现该语言特定行为的函数，生成可执行代码；
     
     我们来看看编译器如何为OC类和对象生成可执行代码，以及如何实现对象消息；
     
     1.生成对象消息传递代码
     
     当编译器解析对象消息（发送消息的表达式）时，如：
        [接收器 消息]
        它会生成 调用运行时系统库中函数objc_msgSend()的代码；
            该函数将接收器、选择器和消息传递的参数作为输入参数；
            因此，编译器会将源代码中的所有消息传递表达式（[接收器 消息]形式的），转换为调用运行时系统库函数objc_msgSend(...)的代码，并为这些调用代码提供源代码所提供的参数；
            每条消息都是以动态的方式处理的，这意味着接收器的类型和方法的实际实现代码都是在运行程序时决定的；
        对于源代码中的类和对象来说，编译器创建了执行对象消息操作所需要的数据结构；
     
     2.生成类和对象的代码
     
     当编译器解析含有类定义和对象的OC源码时；
        它会生成 相应的运行时数据结构；
            OC中的类与运行时系统库中的Class数据结构对应；
            Class数据类型是指向带objc_class标识符的不透明数据类型的指针，如：
                typedef struct objc_class * Class；
     
     不透明数据类型是一种接口定义不完整的C语言结构类型；
        不透明类型提供了一种数据隐藏模式；
        因为其变量只能由专门为它们定义的函数访问；
        使用运行时系统库中的函数可以访问Class（即objc_class）数据类型的变量；
     
     OC类拥有运行时的数据类型，与之类似，OC对象也有；
        编译器在解析OC对象的源代码时，会生成创建运行时对象类型的可执行代码；
        这种数据类型是一种带有objc_object标识符的C语言结构；
     
     可以大致看下objc_object数据类型：
        struct objc_object{
            Class isa;
            '/'*...含有实例变量值的长度可变数据...'*'/
        }
     当你编写的程序创建对象时，系统会为objc_object类型数据分配内存；
     可以看到，这种数据由isa指针后跟实例变量的数据组成；
     
     我们注意到，上述结构中isa是Class结构的；
        像Class数据类型一样，objc_object类型也含有Class类型的isa变量，换言之，该变量就是指向objc_class类型变量的指针；
        事实上，所有OC对象和类的运行时类型都是以isa指针开头的；
     
     与OC中id数据类型对应的运行时数据类型是一种C语言结构，该结构被定义为指向objc_object的指针；
        typedef struct objc_object{
            Class isa；
        } * id；
        也就是说，id就是一个带有objc_object标识符的、指向C语言结构的指针；
        同样，OC块对象也拥有相应的运行时数据结构，因此运行时系统也能够以适当的方式管理他们；
     
     3.查看运行时系统的数据结构
     
     掌握了以上概念，我们来看一个例子；
     新建C8TestClass1类；
     （Code1）
     
     测试代码如下：
     */
    C8TestClass1 * tc1A = [[C8TestClass1 alloc] init];
    tc1A->myInt = 0x5a5a5a;

    C8TestClass1 * tc1B = [[C8TestClass1 alloc] init];
    tc1B->myInt = 0xc3c3c3;

    long tc1Size = class_getInstanceSize([C8TestClass1 class]);
    NSData * obj1Data = [NSData dataWithBytes:(__bridge const void *)(tc1A) length:tc1Size];
    NSData * obj2Data = [NSData dataWithBytes:(__bridge const void *)(tc1B) length:tc1Size];

    NSLog(@"C8TestClass1 object tc1 contains %@",obj1Data);
    NSLog(@"C8TestClass1 object tc2 contains %@",obj2Data);
    NSLog(@"C8TestClass1 memory address = %p",[C8TestClass1 class]);

    /*
     log:
     2017-12-07 16:38:41.888771+0800 精通Objective-C[69375:18798951] C8TestClass1 object tc1 contains <88096101 01000000 5a5a5a00 00000000>
     2017-12-07 16:38:41.888945+0800 精通Objective-C[69375:18798951] C8TestClass1 object tc2 contains <88096101 01000000 c3c3c300 00000000>
     2017-12-07 16:38:41.889044+0800 精通Objective-C[69375:18798951] C8TestClass1 memory address = 0x101610988
     
     分析下这段代码：
     1）导入运行时头文件；
     2）新建测试类，定义了一个全局的实例变量；
     3）使用NSData类获取已经创建对象的数据（以字节为单位）；
        这里还是用了运行时系统库函数class_getInstanceSize获取类实例的尺寸（以字节为单位）；
     
     运行我们得到了上述log；
     
     编译器解析对象时，就会生成objc_object类型的实例；
        该实例由一个isa指针和对象实例变量的值构成；
        因此上述log中对象tc1包含的两项内容：
            一个是isa指针（88096101 01000000）
            一个是对象实例变量的值（5a5a5a00 00000000）
        同样的对象tc2也包含的两项内容：
            一个是isa指针（88096101 01000000）
            一个是对象实例变量的值（c3c3c300 00000000）
     注意：
        对象objc_object数据结构中的第一项就是其isa指针；
        两个对象的isa指针都是相同的，因为他们是同一个类的实例，因此拥有相同的指针值；
        这个isa指针，指向该类的内存地址；
     
     这里可能会一会我们打印的[C8TestClass1 class]的内存地址是0x101610988，与前面显示的指针值不一样；
        实际是相同的，这个程序是在MacPro上运行的，而这种计算机使用的是低字节序（little-endian），换言之它们会使用翻转的字节顺序存储数据；
        这个0x101610988是以翻转的字节顺序显示的；
     */
    id testClz = objc_getClass("C8TestClass1");
    long tcSize = class_getInstanceSize([testClz class]);
    NSData * tcData = [NSData dataWithBytes:(__bridge const void * _Nullable)(testClz) length:tcSize];
    NSLog(@"C8TestClass1 class contains %@",tcData);
    NSLog(@"C8TestClass1 superclass memory address = %p",[C8TestClass1 superclass]);

    id testClr = objc_getClass("C8TestClass2");
    long tcrSize = class_getInstanceSize([testClr class]);
    NSData * tcrData = [NSData dataWithBytes:(__bridge const void * _Nullable)(testClr) length:tcrSize];
    NSLog(@"C8TestClass2 class contains %@",tcrData);
    NSLog(@"C8TestClass2 superclass memory address = %p",[C8TestClass2 superclass]);
    
     /*
      log:
      2017-12-07 17:59:13.679936+0800 精通Objective-C[70080:18859295] C8TestClass1 object tc1 contains <e87a9a0c 01000000 5a5a5a00 00000000>
      2017-12-07 17:59:13.680099+0800 精通Objective-C[70080:18859295] C8TestClass1 object tc2 contains <e87a9a0c 01000000 c3c3c300 00000000>
      2017-12-07 17:59:13.680194+0800 精通Objective-C[70080:18859295] C8TestClass1 memory address = 0x10c9a7ae8
      
      2017-12-07 17:59:13.680303+0800 精通Objective-C[70080:18859295] C8TestClass1 class contains <c07a9a0c 01000000 a81e960d 01000000>
      2017-12-07 17:59:13.680387+0800 精通Objective-C[70080:18859295] C8TestClass1 superclass memory address = 0x10d961ea8
      2017-12-07 17:59:13.680515+0800 精通Objective-C[70080:18859295] C8TestClass2 class contains <107b9a0c 01000000 a81e960d 01000000>
      2017-12-07 17:59:13.680631+0800 精通Objective-C[70080:18859295] C8TestClass2 superclass memory address = 0x10d961ea8
      
      再来看一下这段log：
        前三个打印还是之前对象包含的isa指针和实例变量的值；
        C8TestClass1类含有的数据是一个isa指针（c07a9a0c 01000000）和另一个值（a81e960d 01000000）；
        这个值实际上是指向该类的父类的指针；
      
      我们之前介绍过：
        Class数据类型和objc_object类型都含有Class类型的isa变量，这里也可以看到类的数据结构确实拥有isa指针；
        这里我又新建了一个类C8TestClass2，做了同样的操作，由于和C8TestClass1都继承了NSObject类，所以可以看到打印类中含有的后一个数据是相同的，都是指向父类的指针；
      
      这里大家可能会有一个疑问：
        对象的isa指针是指向类的地址，那么类的isa指针指向的又是什么呢？
        我们先留下这个问题，一会会解答；
      
      现在我们已经了解到了编译器在运行时系统中的作用，使用运行时系统API检查程序来观察编译器生成的数据结构；
      接下来再看看运行时系统库及其实现细节，相信我，你会学到很多！
      
      ===
      
     8.1.2 运行时系统库
     
      苹果公司提供的OC运行时系统库实现了OC的面向对象特性和动态属性；
        多数情况下，运行时系统库是在后台起作用，但也拥有公共的API；
        我们可以在代码中直接使用它们；
      
      这些API是用C语言编写的，含有一系列函数、数据类型和语言常量；
        之前已经介绍过一些关键的系统数据类型，如objc_object、objc_class，和一些函数；
      
      运行时系统库数据类型分为下列几类：
      1）类定义数据结构（类、方法、实例变量、分类、IMP和SEL等）；
      2）实例数据类型（id、objc_object和objc_super）；
      3）值（BOOL）；
      
      函数分为下列几类：
      1）对象消息；
      2）类函数；
      3）实例函数；
      4）协议函数；
      5）方法函数；
      6）属性函数；
      7）选择器函数；
      
      此外，运行时系统库还定义了几个布尔型常量（YES、NO）和空值（NULL、nil和Nil）；
      
      运行时系统库的公共API是在头文件runtime.h中声明的；(可查看相关苹果开放资源-开源文档https://opensource.apple.com )
      接下来我们来简单实践下；
      
      1.使用运行时系统库API创建类
      为了集中说明，我们新建一个类来进行实践：C8DynamicClass
      */
    C8DynamicClass * dyna = [[C8DynamicClass alloc]init];
    [dyna dynamicClassCreate];
    /*
     log:
     2017-12-12 14:11:50.513944+0800 精通Objective-C[8950:739960] Hello world

     我们看到，动态创建的类dynamicClass被正确使用了，通过消息传递成功调用了相应的方法；
     我们来具体看下：
        必须使用#import <objc/message.h>这条语句，才能将运行时系统库中的消息传递API（如objc_msgSend()）包含到文件中；
        创建新类的逻辑：
            创建了一个类对（类及其元类：元类的理解很重要，稍后会介绍）；
            向这个类添加了一个指向greeting()函数的方法；
            之后，在运行时注册这个类；
        这样就能创建这个类的实例；
        方法签名是通过使用拥有相同签名的方法获取的（NSObject的description方法）；
        最后，创建一个类实例并向其发送一条消息；
     
     2.实现运行时系统的对象消息传递
     运行时系统库含有可用于访问下列信息的函数（括号中为函数名）：
     1）对象的类（objc_getClass）
     2）类的父类（class_getSuperclass）
     3）对象的元类（objc_getMetaClass）
     4）类的名称（objc_getName）
     5）类的版本信息（class_getVersion）
     6）以字节为单位的类尺寸（class_getInstanceSize）
     7）类的实例变量列表（class_copyIvarList）
     8）类的方法列表（class_copyMethodList）
     9）类的协议列表（class_copyProtocolList）
     10）类的属性列表（class_copyProperyList）
     
     运行时系统库数据类型和函数为运行时系统库提供了实现各种OC特性（如对象消息传递）所需的数据类型和函数；
     
     运行时系统中的消息传递操作：
        我们先来熟悉一个结构；
                                        根类（NSObject）：方法的虚函数表
                                ^                                           ^
                                | (super指针)                               |(super指针)
                    父类：实例方法的虚函数表                         父类：类方法的虚函数表
                        ^                                                            ^
                        |(super指针)                                                 |(super指针)
        类：实例方法的虚函数表    -----------------     isa指针    ------------------>  元类：类方法的虚函数表
                ^
                |(isa指针)
            对象：实例变量
     （Pic8_0）
        当程序向对象发送消息时；
            运行时系统会通过自定义代码中的类方法缓存和虚函数表，查找类的实例方法；
            为了找到该方法，运行时系统会搜索整个类层次结构，找到后，就会执行该方法的实现代码；
     
     运行时系统库含有很多实现对象消息传递的设计机制；
     我们从虚函数表开始，介绍几个这类机制；
     1）通过虚函数表查找方法：
     运行时系统库定义了一种方法数据类型（objc_method）:
        struct objc_method{
            SEL method_name;
            char * method_types;
            IMP method_imp;
        };
        typedef objc_method Method;
     
     我们来仔细看下这个结构：
        method_name描述方法的名称，类型为SEL；
        method_types变量描述了方法参数的数据类型；
        method_imp是一个类型为IMP的变量，它在方法被调用时提供方法的地址（C语言函数至少会拥有两个参数，self和_cmd）；
     
     因为在执行程序的过程中调用方法的操作可能会执行很多次，所以运行时系统需要使用一种快速高效的方法查询和调用机制；
        虚函数表也称为 分派表，是编程语言中常用的动态绑定支持机制；
        OC运行时系统库实现了一种自定义的虚函数表分派机制，专门用于最大限度地提高性能和灵活性；
     
     虚函数表：
        是一个用于存储IMP类型数据的数组；（IMP类型指向OC方法的实现代码）；
        每个运行时系统类实例（objc_class）都有一个指向虚函数表的指针；
        每个类还拥有最近调用过方法的指针缓存；
     
     这样就优化了方法调用操作的性能；
     
     我们来看一下执行方法查询操作的运行时系统逻辑：（∨∧）
        通过对象的isa指针，获得对象所属的类  <————————————————————————————————————————
            |                                                                                                                 |
            ∨                                                                                                                |
        通过搜索类方法缓存，查找方法对应的IMP指针                                                                             |
            |                                                                                                                 |
            ∨                   （否）                                                                                       |
        判断是否找到方法————————————> 搜索类的虚函数表，查找方法的IMP指针                                         |
            |（是）                                                    |                                                      |
            ∨                                      （是）             ∨                                                     |
        跳转到存储方法代码的地址并执行方法 <——————————判断是否找到方法————（否）                                |
                                                                                           |                                  |
                                                                                           ∨                                 |
     动态方法决议：尝试使用resolveInstanceMethod:或resolveClassMethod:方法，以动态的方式处理有问题的代码（第7章）——————>|
                                                                                           |                                  |
                                                                                           ∨                                 |
                快速消息转发：当选择器出问题时，尝试使用forwardingTargetForSelector:方法向目标转发消息（第三章） ——————>|
                                                                                           |                                  |
                                                                                           ∨                                 |
                标准消息转发：尝试使用forwardInvocation:和methodSignatureForSelector:执行标准转发；              ——————>|
                    如果找不到方法特征。运行时系统会发送一条doesNotRecognizeSelector:消息；
     （Pic8_1）
     
     如果通过虚方法表寻找IMP指针，并且找到了，会将这个IMP存储在缓存中备用；
     这种设计使运行时系统能够执行快速高效的方法查询操作；
     
     2）通过dyld共享缓存使选择器拥有唯一性；
     dyld是什么？
        dyld是一种服务，用于定位和加载动态库；
        它含有共享缓存，能够使多个进程共用相应的动态库；
     
     dyld共享缓存：
        共享缓存中含有一个选择器表，从而使运行时系统能够通过该缓存访问共享库和自定义类的选择器；
        运行时系统就是通过dyld共享缓存 获取共用库的选择器；
     
     dyld共享缓存 与 选择器拥有唯一性的关系？
        启动OC程序的额外开销与确保选择器唯一性时间成正比；
        在一个可执行程序中选择器名称必须有唯一性，但实际开发中很多选择器都是重名的；
        所以，运行时系统需要为每个选择器名称选择一个规范的SEL指针；然后为每次调用和各个方法更新元数据，以便获取唯一值；
        这个过程必须在启动应用程序时执行并且会消耗系统资源（内存和程序启动时间）；
        为了提高该过程的效率，运行时系统才通过dyld共享缓存使选择器拥有唯一性；
     （把所有的选择器都加到一个表里，不重复就唯一了）
     
     3）消息分派：
     在使用方法缓存和虚函数表获取IMP指针之后，运行时系统就会使用自己优化的自定义汇编语言代码实现的分派方法；
        这种代码有时也被称为蹦床，作用就是找到正确的代码并且跳过去；
        我们知道objc_msgSend()函数一定会寻找与消息指定的接收器和选择器对应的IMP指针，然后跳转到该指针指向的地址执行此处存储的代码；
        蹦床代码实现的就是优化之后的这一过程；
     
     为什么要使用蹦床代码（这是一种消息分派方法）？
        这是因为在OC程序中，通过消息执行的调用操作无处不在，仅在启动时，运行时系统库函数objc_msgSend()（作用是执行消息传递）就会调用数百万次；
        因此，一点点的优化都会带来很大的改善；
     
     4）访问类实例方法：
     前面介绍了运行时系统查找和调用实例方法的方式，再来看看运行时系统库处理类方法的方式（即对类方法执行对象消息操作）；
     
     实际上OC中的类也是对象，因此他们也能接收消息；
        如：N[SObject alloc];
        这条语句展示了向类发送消息的方式；
     
     那问题来了，运行时系统如何找到调用方法的呢？
        运行时系统通过元类实现这个功能；
     
     元类：（metaclass）
        是一种特殊的对象，运行时系统使用其中含有的信息能够找到并调用类方法；
        在OC中，每一个类都拥有一个独一无二的元类（因为每个类有可能拥有一个独一无二的类方法列表）；
        运行时系统库提供了访问元类的函数；
     
     可以像常规类一样使用元类，使用元类可以查看类的层次结构；
        正如某各类可以通过父类指针指向它的父类一样，元类也可以通过父类指针指向其对应父类的元类；
        参考（Pic8_0）；
        基类的元类会使用其父类指针指向该基类本身；
        在这种继承层次结构中，所有实例、类和元类都是从基类继承而来的；
     
     对象的isa变量会指向描述该对象的类；
        因此可以使用该变量访问这个对象的实例方法、实例变量等；
        在OC中，类（对象）的isa变量会指向描述类（及其类方法等）的元类；
     
     综上，运行时系统通过下列方式对实例和类方法执行对象消息传递操作；
     1）当源代码向对象发送消息时，运行时系统会通过相应的类实例方法虚函数表，获得合适的实例方法实现代码并跳转执行该方法；
     2）当源代码向类发送消息时，运行时系统会通过该类的元类类方法虚函数表，获得合适的类方法实现代码并跳转执行该方法；
     
     讲了很多内容了，希望大家注意理解，接下来通过一个例子梳理下这些概念；
     
     3.检查类方法
     为弄清楚运行时系统数据结构使用元类的方式，以及元类在整个层次结构中的作用；我们通过一段代码理解下；
     我们需要使用上一节定义的类C8TestClass1：
     @interface C8TestClass1:NSObject{
     @public
     int myInt;
     }
     @end
     
     @implementation C8TestClass1
     
     @end
     */
    id metaClass = objc_getMetaClass("C8TestClass1");
    long mclzSize = class_getInstanceSize([metaClass class]);
    NSData * mclzData = [NSData dataWithBytes:(__bridge const void *)metaClass length:mclzSize];
    
    NSLog(@"C8TestClass1 metaClass contains %@",mclzData);
    class_isMetaClass(metaClass)?NSLog(@"Class %s is a metaclass",class_getName([metaClass class])):NSLog(@"Class %s is not a metaclass",class_getName([metaClass class]));
    
     /*
      log:(我们把上一节的代码一起运行，log如下)
      2017-12-13 11:07:17.884517+0800 精通Objective-C[14709:1595535] C8TestClass1 object tc1 contains <a0dc1909 01000000 5a5a5a00 00000000>
      2017-12-13 11:07:17.884712+0800 精通Objective-C[14709:1595535] C8TestClass1 object tc2 contains <a0dc1909 01000000 c3c3c300 00000000>
      2017-12-13 11:07:17.884808+0800 精通Objective-C[14709:1595535] C8TestClass1 memory address = 0x10919dca0
      2017-12-13 11:07:17.884940+0800 精通Objective-C[14709:1595535] C8TestClass1 class contains <78dc1909 01000000 a8ae150a 01000000>
      2017-12-13 11:07:17.885035+0800 精通Objective-C[14709:1595535] C8TestClass1 superclass memory address = 0x10a15aea8
      2017-12-13 11:07:17.885141+0800 精通Objective-C[14709:1595535] C8TestClass2 class contains <c8dc1909 01000000 a8ae150a 01000000>
      2017-12-13 11:07:17.885232+0800 精通Objective-C[14709:1595535] C8TestClass2 superclass memory address = 0x10a15aea8
      2017-12-13 11:07:17.885364+0800 精通Objective-C[14709:1595535] Hello world
      2017-12-13 11:07:17.885493+0800 精通Objective-C[14709:1595535] C8TestClass1 metaClass contains <58ae150a 01000000 58ae150a 01000000 c02f1100 80600000 07000000 01000000 c0ef0700 80600000>
      2017-12-13 11:07:17.885581+0800 精通Objective-C[14709:1595535] Class C8TestClass1 is a metaclass
      
      分析：
        首先，objc_getMetaClass()，获取已命名类的元类定义；
        之后，显示了与其关联的数据类型；
        最后，使用了class_isMetaClass()测试对象是否为元类；
      
      这里使用__bridge是因为：
        [NSData dataWithBytes:(__bridge const void *)metaClass length:mclzSize];方法的第一个参数接收的是类型为（const void *）的变量；
        ARC是禁止从OC对象到（void *）类型的直接转换；因此通过桥接转换（第六章），可以使编译器处理这些情况；
        因为不存在所有权转换（赋予变量metaClass的对象仍旧由ARC管理），所以使用了__bridge标记；
    
      再来关注下log：
        元类中有isa指针(58ae150a 01000000)，父类指针(58ae150a 01000000)和附加信息(其他的部分)；
        父类是NSObject；
        元类的isa指针都是执行基类的，这里C8TestClass1继承自NSObject，所以我们会发现isa指针和父类指针的值是相同的，都指向了NSObject类；
      
      这些内容都是对运行时系统API及其实现方式的补充介绍，接下来介绍用于直接与运行时系统交互的OC API；
      
     ==========
      
      8.2 与运行时系统交互
      
      OC程序通过运行时系统交互实现动态特性；
        这些交互分为三个等级：
        1）OB源代码；
        2）Foundation框架中的NSObject类的方法；
        3）运行时系统库API；
      
      ——————    OC源代码   ——————
              框架与服务   运行时系统API
      ——————    编译器     ——————
      ——————  运行时系统库 ——————
      
      前面几节介绍了编译器和运行时系统库的作用，下面介绍Foundation框架中的NSObject类的运行时特性；
     
      8.2.1 NSObject类的运行时方法
      
      OC语言提供了许多动态编程功能：
        运行时系统提供了一系列API，使用它们可以直接与运行时系统进行交互；
        但这些API使用C语言编写的，因此要求使用过程式编程方法；
        作为一种替代解决方案，Foundation框架中的NSObject类提供了一系列方法，使用这些方法可以实现许多与运行时系统API相同的功能；
        继承自NSObject类的类都可以直接使用这些方法；
      
      NSObject类的运行时方法提供的功能：
      1）对象内省；
      2）消息转发；
      3）动态方法决议；
      4）动态加载；
      
      8.2.2 执行对象内省
     
      我们定义一个需要的类C8Greeter；
      编写测试代码如下：
      */
    C8Greeter * greeter = [[C8Greeter alloc] init];
    [greeter setSalutation:@"Hello"];
    
    if ([greeter respondsToSelector:@selector(greeting:)] &&
        [greeter conformsToProtocol:@protocol(NSObject)]) {
        //使用NSObject的运行时方法performSelector：withObject：向实例发送一条消息；
        id result = [greeter performSelector:@selector(greeting:) withObject:@"Monster!"];
        NSLog(@"%@",result);
    }
    
    /*
     log：
     2017-12-13 13:50:36.721951+0800 精通Objective-C[15990:2010671] Hello,Monster!

     这段代码中，使用了NSObject类的运行时方法执行了对象内省；
     满足条件时，还会使用NSObject的运行时方法performSelector：withObject：向实例发送一条消息；
     
      8.3 小结
     
     本章详细介绍了运行时系统结构中的关键组成部分；
     到目前为止，我们至少应该理解了运行时系统实现面向对象特性和动态功能的方式；
     本章要点：
     1）OC运行时有两部分构成：编译器和运行时系统库；
        编译器会接受OC源代码并生成由运行时系统库执行的代码；
        运行时系统库会与所有OC程序链接（在链接阶段）；
        这两个一起实现了OC语言面向对象特性和动态功能；
     2）运行时系统库API定义了一系列数据类型、函数和常量；
        这些数据类型和函数中的多数都与OC语言元素（对象 类 协议 方法 和实例变量等）一一对应；
        运行时系统库的公用API是使用C语言编写的；
     3）运行时系统库实现了各种机制以增强程序的性能和可扩展性；
        典型的有方法缓存、虚函数表和dyld共享缓存；
     4）运行时系统库使用元类查找和调用类方法；
        元类是一种特殊的数据类型，运行时系统使用其中的信息可以查找和调用方法；
     5）Foundation框架中的NSObject类提供了一系列方法，使用可以调用运行时功能和行为；
        这些方法可以执行对象内省、动态方法决议、动态加载和消息转发操作；
     
     本章内容至此就结束了，内容比较多，仔细理解，多有助益。
     
     */
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}



/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end
